﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using Ionic.Zip;
using LynxWiki.Search;
using LynxWiki.DomainModel;

[assembly: InternalsVisibleTo("LynxWiki.Tests")]

namespace LynxWiki.Repository
{
    public class WikiDirectoryRepository : LynxWiki.Infrastructure.IWikiDirectoryRepository
    {
        private string server_path;
        private string root;
        private string path;
        private string currentDir;
        private IPrincipal user;

        public string CurrentDir { get { return currentDir; } }

        //The following are used by export functions 
        private static string findImageString = "(?<start><img src=\")(?<image>/[^:\"]*?)(?<alt>\" alt=\"[^\"]*?)(?<class>\" class=\"[^\"]*?)(?<end>\" />)|(?<start><img src=\")(?<image>/[^:\"]*?)(?<end>\" />)|(?<start><img src=\")(?<image>/[^:\"]*?)(?<alt>\" alt=\"[^\"]*?)(?<end>\" />)";
        private static Regex findImageRegEx = new Regex(findImageString, RegexOptions.Compiled);

        private static string findHRefString = "(?<start><a)(?<title> title=\".[^\"]*?\")(?<cls> class=\"create\")(?<ref> href=\".[^\"]*?\">)";
        private static Regex findHRefRegEx = new Regex(findHRefString, RegexOptions.Compiled);

        List<string> attachments = new List<string>();


        public WikiDirectoryRepository(string root, string path, string currentDir, IPrincipal user = null)
        {
            this.root = root;
            if (path != null) {
                this.path = path;
            }
            else {
                this.path = string.Empty;
            }
            if (currentDir != null) {
                this.currentDir = currentDir;
            }
            else {
                this.currentDir = string.Empty;
            }
            this.user = user;
        }

        public WikiDirectoryRepository(string fullPath, IPrincipal user = null)
        {
            this.root = DomainModel.WikiRoot.RootDirectory;
            int server_part = fullPath.IndexOf(this.root);
            server_path = fullPath.Substring(0, server_part);
            int startPath = root.Length;
            this.path = string.Empty;
            int endPath = fullPath.LastIndexOf(@"\") + 1;
            if (endPath - startPath > server_part) {
                this.path = fullPath.Substring(server_part, endPath - server_part - 1);
            }
            this.currentDir = string.Empty;
            if (endPath > 3) {
                this.currentDir = fullPath.Substring(endPath);
            }
            this.user = user;
        }

        internal WikiDirectoryRepository(string root, string  server_path, string path, string current_dir, IPrincipal user = null)
        {
            this.root = root;
            this.server_path = path;
            this.path = path;
            this.currentDir = current_dir;
            this.user = user;
        }

        public IEnumerable<DomainModel.WikiDirectory> GetSubDirectoryList()
        {
            IList<DomainModel.WikiDirectory> wDir = new List<DomainModel.WikiDirectory>();
            DirectoryInfo dir;
            if (currentDir == "WikiDir" && path == string.Empty)
                dir = new DirectoryInfo(Path.Combine(server_path, root));
            else
                dir = new DirectoryInfo(Path.Combine(server_path, path, currentDir));
            if (dir.Exists) {
                try {
                    IEnumerable<DirectoryInfo> query = from DirectoryInfo directory in dir.GetDirectories() select directory;
                    foreach (DirectoryInfo directory in query) {
                        FileInfo file = (from FileInfo fi in dir.GetFiles() where fi.Name == (directory.Name + "__directory.xml") select fi).First();
                        wDir.Add(new DomainModel.WikiDirectory(File.ReadAllText(file.FullName)));
                    }
                }
                catch (Exception ex) {
                    Debug.Print(ex.Message);
                }
            }
            return wDir as IEnumerable<DomainModel.WikiDirectory>;
        }

        public List<string> GetWikiTopicList()
        {
            DirectoryInfo dir;
            if (currentDir == "WikiDir" && path == string.Empty)
                dir = new DirectoryInfo(Path.Combine(server_path, root));
            else
                dir = new DirectoryInfo(Path.Combine(server_path, path, currentDir));
            if (dir.Exists) {
                IEnumerable<string> query = (from FileInfo file in dir.GetFiles() where file.Name.Contains("__current.xml") select file.Name.Substring(0, file.Name.IndexOf("__current.xml"))).Distinct();
                return query.ToList();
            }
            return null;
        }

        public List<FileInfo> GetWikiTopicFileInfoList()
        {
            DirectoryInfo dir;
            if (currentDir == "WikiDir" && path == string.Empty)
                dir = new DirectoryInfo(Path.Combine(server_path, root));
            else
                dir = new DirectoryInfo(Path.Combine(server_path, path, currentDir));
            if (dir.Exists) {
                IEnumerable<FileInfo> query = (from FileInfo file in dir.GetFiles() where file.Name.Contains("__current.xml") select file).Distinct();
                return query.ToList();
            }
            return null;
        }

        public List<FileInfo> GetWikiTopicFileInfoByModList(int max = 0)
        {
            List<FileInfo> tempList = GetWikiTopicFileInfoList();
            if (tempList != null) {
                tempList = tempList.OrderByDescending(f => f.LastWriteTime).ToList();
                if (max > 0) {
                    tempList = tempList.Take(max).ToList();
                }
            }
            return tempList;
        }

        public void WikiTopicLockAction(string pageName, string action)
        {
            string filePath;
            if (currentDir == "WikiDir" && path == string.Empty)
                filePath = Path.Combine(server_path, root, pageName);
            else
                filePath = Path.Combine(server_path, path, currentDir, pageName);
            if (action.ToLower() == "lock") {
                File.SetAttributes(filePath, File.GetAttributes(filePath) | FileAttributes.ReadOnly);
            }
            else if (action.ToLower() == "unlock") {
                File.SetAttributes(filePath, File.GetAttributes(filePath) & ~FileAttributes.ReadOnly);
            }
        }


        public bool TopicIsLocked(string pageName)
        {
            string filePath;
            if (currentDir == "WikiDir" && path == string.Empty)
                filePath = Path.Combine(server_path, root, pageName);
            else
                filePath = Path.Combine(server_path, path, currentDir, pageName);
            bool IsLocked = ((File.GetAttributes(filePath) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly);
            return IsLocked;
        }


        public bool WikiTopicExists(string topicName)
        {
            string dirPath;
            if (currentDir == "WikiDir" && path == string.Empty)
                dirPath = Path.Combine(server_path, root);
            else
                dirPath = Path.Combine(server_path, path, currentDir);
            DirectoryInfo dir = new DirectoryInfo(dirPath);
            if (dir.Exists) {
                return ((from FileInfo file in dir.GetFiles() where file.Name == (topicName + "__current.xml") select file).Count() > 0);
            }
            else return false;
        }


        public int GetSubDirectoryCount()
        {
            int count = 0;
            DirectoryInfo dir;
            if (currentDir == "WikiDir" && path == string.Empty)
                dir = new DirectoryInfo(Path.Combine(server_path, root));
            else
                dir = new DirectoryInfo(Path.Combine(server_path, path, currentDir));
            if (dir.Exists) {
                count = dir.GetDirectories().Count();
            }
            return count;
        }

        public int GetTopicVersionCount()
        {
            int count = 0;
            DirectoryInfo dir;
            if (currentDir == "WikiDir" && path == string.Empty)
                dir = new DirectoryInfo(Path.Combine(server_path, root));
            else
                dir = new DirectoryInfo(Path.Combine(server_path, path, currentDir));
            if (dir.Exists) {
                dir.GetFiles().Count();
            }
            return count;
        }

        public int GetWikiTopicCount()
        {
            int count = 0;
            DirectoryInfo dir;
            if (currentDir == "WikiDir" && path == string.Empty)
                dir = new DirectoryInfo(Path.Combine(server_path, root));
            else
                dir = new DirectoryInfo(Path.Combine(server_path, path, currentDir));
            if (dir.Exists) {
                count = (from FileInfo file in dir.GetFiles() where file.Name.Contains("__current.xml") select file.Name.Substring(0, file.Name.IndexOf("__current.xml"))).Distinct().Count();
            }
            return count;
        }

        public void Add(DomainModel.WikiDirectory wd)
        {
            DirectoryInfo dir = new DirectoryInfo(Path.Combine(server_path, root, wd.ParentPath, wd.DirectoryName));
            if (!dir.Exists) {
                dir.Create();
                string name = dir.FullName + "__directory.xml";
                File.WriteAllText(name, wd.ToXml(dir.FullName));
            }
        }

        public void Remove(DomainModel.WikiDirectory wd)
        {
            throw new NotImplementedException();
        }

        public void RemoveWikiTopic(string topicName)
        {
            throw new NotImplementedException();
        }


        public DomainModel.WikiDirectory GetWikiDirectoryInfo(string dirName, string dirPath)
        {
            if (dirPath == null)
                dirPath = string.Empty;
            DirectoryInfo dir = new DirectoryInfo(Path.Combine(root, dirPath));
            if (dir.Exists) {
                FileInfo file = new FileInfo(Path.Combine(root, path, dirName + "__directory.xml"));
                if (file.Exists) {
                    return new DomainModel.WikiDirectory(File.ReadAllText(file.FullName));
                }
                else {
                    return null;
                }
            }
            else {
                return null;
            }
        }

        public DomainModel.WikiDirectory GetWikiDirectoryInfo(string fullPath)
        {
            string path = string.Empty;
            int server_part = fullPath.IndexOf(this.root);
            server_path = fullPath.Substring(0, server_part);
            int startPath = root.Length;
            this.path = string.Empty;
            int endPath = fullPath.LastIndexOf(@"\") + 1;
            if (endPath - startPath > server_part) {
                this.path = fullPath.Substring(server_part, endPath - server_part - 1);
            }
            this.currentDir = string.Empty;
            if (endPath > 3) {
                this.currentDir = fullPath.Substring(endPath);
            }
            string dirName = this.currentDir;
            DirectoryInfo dir = new DirectoryInfo(fullPath);
            if (dir.Exists) {
                FileInfo file;
                if (dirName == "WikiDir" && path == string.Empty)
                    file = new FileInfo(Path.Combine(server_path, root + "__directory.xml"));
                else
                    file = new FileInfo(fullPath + "__directory.xml");
                if (file.Exists) {
                    return new DomainModel.WikiDirectory(File.ReadAllText(file.FullName));
                }
                else {
                    return null;
                }
            }
            else {
                return null;
            }
        }

        public void IndexDirectoryFiles()
        {
            DirectoryInfo dir;
            try {
                if (currentDir == "WikiDir" && path == string.Empty)
                    dir = new DirectoryInfo(Path.Combine(server_path, root));
                else
                    dir = new DirectoryInfo(Path.Combine(server_path, path, currentDir));
                if (dir.Exists) {
                    IEnumerable<FileInfo> topics = (from FileInfo file in dir.GetFiles() where file.Name.Contains("__current.xml") select file).Distinct();
                    IEnumerable<FileInfo> discussions = (from FileInfo file in dir.GetFiles() where file.Name.Contains("__discuss.xml") select file).Distinct();
                    List<LynxWiki.DomainModel.SearchData> sdl = new List<DomainModel.SearchData>();
                    foreach (FileInfo topic in topics) {
                        sdl.Add(new DomainModel.SearchData(new DomainModel.TopicVersion(File.ReadAllText(topic.FullName)), "Topic"));
                    }
                    foreach (FileInfo discuss in discussions) {
                        sdl.Add(new DomainModel.SearchData(new DomainModel.TopicVersion(File.ReadAllText(discuss.FullName)), "Discuss"));
                    }
                    LuceneSearch.AddUpdateLuceneIndex(sdl);
                }
            }
            catch (Exception ex) {
                Debug.Print(ex.Message);
            }
        }

        public byte[] ExportDirectoryToHtml()
        {
            try {
                DirectoryInfo dir;
                DirectoryInfo tempDir = new DirectoryInfo(Path.Combine(server_path, "App_Data", "Temp", "temp_" + DateTime.Now.Ticks.ToString()));
                if (!tempDir.Exists) {
                    tempDir.Create();
                }
                StringBuilder indexFile = new StringBuilder();
                if (currentDir == "WikiDir" && path == string.Empty)
                    dir = new DirectoryInfo(Path.Combine(server_path, root));
                else
                    dir = new DirectoryInfo(Path.Combine(server_path, path, currentDir));
                if (dir.Exists) {
                    indexFile.AppendLine("<ul>");
                    IEnumerable<FileInfo> topics = (from FileInfo file in dir.GetFiles() where file.Name.Contains("__current.xml") select file).Distinct();
                    foreach (FileInfo topic in topics) {
                        TopicVersion tv = new TopicVersion(File.ReadAllText(topic.FullName));
                        if (tv.UserReadAccess(user)) {
                            indexFile.AppendFormat(@"<li><a href=""{0}.html"">{0}</a></li>", tv.TopicName);
                            indexFile.AppendLine();
                            string tempHtml = TopicToHtmlForExport(tv.HtmlText, tv.TopicName);
                            string tempFile = Path.Combine(tempDir.FullName, tv.TopicName + ".html");
                            while (findImageRegEx.IsMatch(tempHtml)) {
                                tempHtml = findImageRegEx.Replace(tempHtml, new MatchEvaluator(replaceImageLink));
                            }

                            while (findHRefRegEx.IsMatch(tempHtml)) {
                                tempHtml = findHRefRegEx.Replace(tempHtml, new MatchEvaluator(replaceHRefLink));
                            }

                            File.WriteAllText(tempFile, tempHtml);
                        }
                    }
                }
                indexFile.AppendLine("</li>");
                string indexName = Path.Combine(tempDir.FullName, "Index_" + currentDir + ".html");
                string indexHtml = TopicToHtmlForExport(indexFile.ToString(), "Index_" + currentDir);
                File.WriteAllText(indexName, indexHtml);
                using (ZipFile zip = new ZipFile()) {
                    zip.AddDirectory(tempDir.FullName);
                    zip.AddDirectory(Path.Combine(server_path, "Content"), "css");
                    zip.AddDirectory(Path.Combine(server_path, "Scripts"), "Scripts");
                    zip.AddDirectoryByName("attachments");
                    foreach (string attach in attachments) {
                        try {
                            zip.AddFile(WikiRoot.HostWikiPath.Substring(0, WikiRoot.HostWikiPath.LastIndexOf("LynxWiki")) + attach.Replace("/", "\\"), "attachments");
                        }
                        catch { }  //ensure no ErrorEventArgs on try to insert or attach duplicate  missing file
                    }
                    using (MemoryStream ms = new MemoryStream()) {
                        zip.Save(ms);
                        tempDir.Delete(true);
                        return ms.ToArray();
                    }
                }
            }
            catch (Exception ex) {
                Debug.Print(ex.Message);
                throw;
            }
            //return null;
        }

        public string TopicToHtmlForExport(string htmlString, string topicName)
        {
            StringBuilder htmlOut = new StringBuilder();
            htmlOut.AppendLine("<!DOCTYPE html>");
            htmlOut.AppendLine(@"<html lang=""en"">");
            htmlOut.AppendLine(@"<head>");
            htmlOut.AppendLine(@"<meta charset=""utf-8"" />");
            htmlOut.AppendFormat(@"<title>LynxWiki - {0}</title>\r\n", topicName);
            htmlOut.AppendLine(@"<link href=""favicon.ico"" rel=""shortcut icon"" type=""image/x-icon"" />");
            htmlOut.AppendLine(@"<meta name=""viewport"" content=""width=device-width"" />");
            htmlOut.AppendLine(@"<link href=""css/site.css"" rel=""stylesheet""/>");
            htmlOut.AppendLine(@"<link href=""css/SitePrint.css"" rel=""stylesheet""/>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shCore.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushBash.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushCSharp.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushCss.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushDiff.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushJava.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushJScript.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushPerl.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushPhp.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushPlain.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushPowerShell.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushPython.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushRuby.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushSql.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushVb.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/SyntaxHighlighter/shBrushXml.js""></script>");
            htmlOut.AppendLine(@"<link href=""css/SyntaxHighlighter/shCore.css"" rel=""stylesheet""/>");
            htmlOut.AppendLine(@"<link href=""css/SyntaxHighlighter/shThemeDefault.css"" rel=""stylesheet""/>");
            htmlOut.AppendLine(@"<script src=""Scripts/modernizr-2.6.2.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/jsMath/jsMath.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/jsMath/plugins/noImageFonts.js""></script>");
            htmlOut.AppendLine(@"<script type=""text/javascript"">SyntaxHighlighter.all();</script>");
            htmlOut.AppendLine(@"</head>");
            htmlOut.AppendLine(@"<body>");

            htmlOut.AppendLine("<h1>" + topicName + "</h1>");
            htmlOut.Append(htmlString);

            htmlOut.AppendLine(@"<script src=""Scripts/jquery-1.9.0.js""></script>");
            htmlOut.AppendLine(@"<script src=""Scripts/jquery-ui-1.9.2.js""></script>");
            htmlOut.AppendLine(@"<script type=""text/javascript"">");
            htmlOut.AppendLine(@"$(document).ready(function () {");
            htmlOut.AppendLine(@"jsMath.Process(document);");
            htmlOut.AppendLine(@"});");
            htmlOut.AppendLine(@"</script>");
            htmlOut.AppendLine(@"</body>");
            htmlOut.AppendLine(@"</html>");


            return htmlOut.ToString();
        }

        private string replaceImageLink(Match match)
        {
            string replacement = string.Empty;
            string start = match.Groups["start"].Value;
            string imageLink = match.Groups["image"].Value;
            string alt = match.Groups["alt"].Value;
            string classStr = match.Groups["class"].Value;
            string end = match.Groups["end"].Value;
            attachments.Add(imageLink);
            if (imageLink.Contains("/")) {
                imageLink = "attachments" + imageLink.Substring(imageLink.LastIndexOf("/"));
            }
            replacement = start + imageLink + alt + classStr + end;
            return replacement;
        }

        private string replaceHRefLink(Match match)
        {
            string replacement = string.Empty;
            string start = match.Groups["start"].Value;
            string hrefLink = match.Groups["ref"].Value;
            string title = match.Groups["title"].Value;
            string classStr = match.Groups["cls"].Value;
            if (hrefLink.Contains("/")) {
                hrefLink = hrefLink.Substring(0, hrefLink.Length - 2);
                hrefLink = " href=\"" + hrefLink.Substring(hrefLink.LastIndexOf("/") + 1) + ".html\">";
            }
            replacement = start + hrefLink;
            return replacement;
        }


    }
}
